#include <UnitGroupManager.h>
#include "AttackManager.h"
#include "EngageManager.h"

AttackManager::AttackManager(Arbitrator::Arbitrator<Unit*,double>* arbitrator)
{
  this->arbitrator = arbitrator;
  this->target = NULL;
  this->retreatDrop = 0;
}

AttackManager::~AttackManager()
{
}

void AttackManager::onOffer(std::set<Unit*> units)
{
  // Skip this if we are max supply
  if (Broodwar->self()->supplyUsed() < 400) {
    // how many healthy mutas do we currently have
    int numHealthyMutas = TheInformationManager->getHealthyMutasCount();

    // if not equal to attack quota and is idle,  or 
    // if retreating from an attack
    if ( (numHealthyMutas < ATTACK_THRESHOLD && this->target == NULL) || (numHealthyMutas < RETREAT_THRESHOLD  && this->target != NULL) ){
      for each (Unit* u in units)
        arbitrator->decline(this, u, 0);
      return;
    }
  }

  // else, 
  // Accepting everything that we bid on for now over the health thresh
  for each (Unit* u in units) {
    if (TheDefenseManager->isHealed(u))  {
      this->attackers.insert(u);
      arbitrator->accept(this, u);
      // Raise bid to 60 to hold it over MorphManager
      arbitrator->setBid(this, u, 60);
    } else {
      arbitrator->decline(this, u, 0);
    }
  }
}

void AttackManager::onRevoke(Unit* unit, double bid)
{
  this->onRemoveUnit(unit);
}

void AttackManager::onRemoveUnit(Unit* unit)
{
  TheEngageManager->dismiss(unit);
  this->attackers.erase(unit);
}

void AttackManager::update()
{
  // Bid on all completed Mutalisks
  std::set<Unit*> mutas = SelectAll(BWAPI::UnitTypes::Zerg_Mutalisk)(isCompleted);

  if (Broodwar->self()->supplyUsed() < 400) {
    // Get new attackers
    for each (Unit* u in mutas) {
      // Bid if they are healed and do not already belong to us
      if ( TheDefenseManager->isHealed (u) && attackers.find(u) == attackers.end() ) {
        this->arbitrator->setBid(this, u, 30);
      }
    }

    // Check to make sure we aren't too light
    if (this->attackers.size() < RETREAT_THRESHOLD && this->target != NULL ) {
      // Halt attacking until we rebuild
      // Force move to our closest base from the target point -- will use
      // potentials here later to avoid all unit contact
      BaseLocation* tarBase = NULL;
      double closest = 0;
      for each (Base* base in TheBaseManager->getAllBases()) { 
        double dist = base->getBaseLocation()->getAirDistance(this->target);
        if (tarBase == NULL || dist < closest) {
          closest = dist;
          tarBase = base->getBaseLocation();
        }
      }
      if (tarBase) {
        for each (Unit* u in this->attackers) {
          u->move(tarBase->getPosition());
        }
      }
      // Give us 10 seconds to retreat before we turn over control
      this->retreatDrop = Broodwar->getFrameCount() + 10*24;

      this->target = NULL; // abort mission
      return;
    }

    if (this->attackers.size() < ATTACK_THRESHOLD && this->target == NULL ) {
      // Check for retreat release
      if (this->retreatDrop > 0 && Broodwar->getFrameCount() > this->retreatDrop) {
        Broodwar->printf("releasing attackers!");
        this->arbitrator->removeBid(this, this->attackers);
        this->retreatDrop = 0;
      }
      return ; // hold off until we've amassed a sufficient number
    }
  } else {
    // Max supply army - time to all in
    for each (Unit* u in Broodwar->self()->getUnits()) {
      if (u->getType().canAttack() && !u->getType().isWorker() && !u->getType().isBuilding()) {
        this->arbitrator->setBid(this, u, 30);
      }
    }
  }

  // If we get this far, we are not retreating
  this->retreatDrop = 0;
  
  // Pick a target if we do not have one
  bool newTarget = false; // flag for later
  if (this->target == NULL && !this->attackers.empty()) {
    newTarget = true;
    this->findNewTarget();
  }

  // Carry out attack, very basic for now
  // Make sure we have a target though
  if (target) {
    int frame = BWAPI::Broodwar->getFrameCount();
    // Run the scan frequently, issue commands less so
    if (Broodwar->getFrameCount() % 4 == 0) {
      // Check surrounding (enemy) units to see if base is clear
      std::set<Unit*> surrounding;
      for each (Unit* sUnit in TheInformationManager->getAllKnownEnemy()) {
        TilePosition pos = TilePosition(TheInformationManager->getLastPosition(sUnit));
        if (pos.isValid())
          if (BWTA::getRegion(pos) == target->getRegion())
            surrounding.insert(sUnit);
      }

      for each (Unit* u in this->attackers) {
        //TheEngageManager->engage(u, target->getPosition());
        if (newTarget || (frame > nextCommand[u] && !u->isAttacking())) {
          u->attack(target->getPosition());
          nextCommand[u] = frame + 5*24; // 5s before reassignment
        }

        // If the unit is not attacking anything and is within 10 units of the destination
        if (!u->isAttacking() && u->getDistance(target->getPosition()) < 80) {
          bool found = false;
          for (std::set<Unit*>::iterator nearby = surrounding.begin(); nearby != surrounding.end(); ++nearby) {
            // Issue a quick order towards whichever unit it found
            //TheEngageManager->engage(u, (*nearby)->getPosition());
            Position tarPos = TheInformationManager->getLastPosition(*nearby);
            u->attack(tarPos);
            nextCommand[u] = frame + 8*24; // 8s before reassignment
            // Only set found if:
            //  - We are sure it exists
            //  - This is an unknown target that was picked this frame (to allow  others to assist)
            //  - This is an unknown target that was picked before the last seen
            //  - This is an unknown target that was picked before this base was chosen
            // This way, if a unit moved away, we check the spot once and ignore 
            // it unless we see the unit again or come back to the base.
            if ((*nearby)->exists() || 
                this->lastAttackAttempt[*nearby] == Broodwar->getFrameCount() ||
                this->lastAttackAttempt[*nearby] < TheInformationManager->getLastSeenTime(*nearby) ||
                (target && this->lastAttackAttempt[*nearby] < lastAttacked[target])) {
              this->lastAttackAttempt[*nearby] = Broodwar->getFrameCount();
              found = true;
              break;
            }
          }
          if (!found) {
            // Assume base is empty
            TheInformationManager->setBaseEmpty(target);
            // Clear our target and repick it
            this->target = NULL;
            this->findNewTarget();
            break;
          }
        }
      }
    }
  }
}

void AttackManager::findNewTarget()
{
  BaseLocation *newTarget = NULL;
  std::set<BaseLocation*> enemyBases = TheInformationManager->getEnemyBases();

  // We do not have a target yet, picking the nearest base for testing purposes
  double nearest = -1;
  int last = Broodwar->getFrameCount()+1;
  std::set<BaseLocation*> targetBases;

  // A "scoutAttack" is one that does not have a guaranteed hostile target
  // In that case, figure out where most of our mutalisks are
  bool scoutAttack = enemyBases.empty();
  int x=0, y=0;
  if (scoutAttack) {
    targetBases = BWTA::getBaseLocations();
    for each (Unit* u in this->attackers) {
      x += u->getPosition().x();
      y += u->getPosition().y();
    }
    x /= this->attackers.size();
    y /= this->attackers.size();
  } else {
    targetBases = enemyBases;
  }
  Position mutaCenter(x, y);

  for each (BaseLocation* base in targetBases) {
    // If it's not flagged as an enemy base and is flagged as our base, ignore
    // it. I am checking both because there's a chance that BaseManager will
    // incorrectly flag one and never unflag it. This is a longterm TODO
    if (!TheInformationManager->isEnemyBase(base) && TheBaseManager->getBase(base)) 
      continue;

    // If we are using enemy bases, just use distance. If we are using all
    // bases, use lastAttacked to determine which base we should check out
    if (scoutAttack) {
      if (lastAttacked[base] < last) {
        last = lastAttacked[base];
        newTarget = base;
      } else if (lastAttacked[base] == last) {
        // use distance from mutalisks for a tie
        if (mutaCenter.getDistance(base->getPosition()) < mutaCenter.getDistance(newTarget->getPosition())){
          // use this one
          last = lastAttacked[base];
          newTarget = base;
        }
      }
    } else {
      // Hostile attacks are done via distance. This isn't the best, but it works
      double dist = BWTA::getStartLocation(Broodwar->self())->getAirDistance(base);
      if (!newTarget || dist < nearest) {
        nearest = dist;
        newTarget = base;
      }
    }
  }

  this->target = newTarget;
  lastAttacked[target] = Broodwar->getFrameCount();
}

std::string AttackManager::getName() const
{
  return "AttackManager";
}

std::string AttackManager::getShortName() const
{
  return "Atk";
}

